ARQ retry-limit sweep + post-ARQ FEC dimensioning (#362 steps 1+4) - #367
Conversation
…measured curve in docs tests/arq_retry_sweep.sh runs the arq_e2e bench once per DEVOURER_TX_RETRY_LIMIT and tabulates delivered% / drops / mean retries (the airtime proxy), then hands each run to tests/arq_fec_dimension.py — the residual gap-length distribution a wfb-style (K,N) block FEC must cover, with the single-gap-per-window caveat and the conservative unreported-counts-as-lost accounting stated in the header. Measured (8812CU retrying TX -> 8812EU duplex ground airing PixelPilot-shaped bursts, ~1 k fps, near-field): limit 3 = 99.72% delivered with a 0.26% residual; 8 = 99.97%, residual 0.03% with gaps <= 3 (K8/N11 covers); 16 = 100.00% at mean 0.054 retries/frame; 32 = no gain for +17% more retries. Queue-time p99 flat across limits. docs/scheduled-mac.md carries the curve and the recommendation (16 on an ARQ link; 8 plus a light FEC floor where airtime is precious). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoAdd ARQ retry-limit sweep harness and post-ARQ FEC residual gap analyzer
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
…irs, ledger guard, nearest-rank percentiles - The sweep summary now breaks drops out per burst phase (drops@6M:10 / drops@6M:30) from each run's own report table — the limit-vs-burst-length interaction is the curve's point (smoke: 12 vs 36 at limit 3). - Each limit's run lands in a deterministic OUT=<sweep>/limit_<L> dir instead of inferring "newest /tmp/arq-e2e/*", which raced concurrent bench runs. - arq_fec_dimension.py refuses empty ledgers loudly (no tagged reports / no rx.seq would count every frame as undelivered — J1-format reports carry no tag) and computes nearest-rank percentiles (int(p*n) was one rank upward-biased); the published sweep extremes are unchanged under the fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s, dead MISSED_RPT_NUM (#363 step 1) (#368) Step 1 of #363 (the issue stays open for SPE_RPT sampling and windowed RX receipts, steps 2–3). ## What the recorded #359/#366/#367 ledgers + a 3-point envelope sweep establish **The 53% coverage collapse is a fw-side CCX emission ceiling, and the fw does not account its own drops.** | demanded fps | coverage | achieved reports/s | |---|---|---| | 1.04 k | 100.0% | 1.04 k (unsaturated) | | 1.26 k | 99.4% | 1.25 k — the knee | | 1.82 k | 77.2% | 1.40 k | | 2.39 k | 53.4–55.6% | 1.27–1.33 k | `coverage ≈ min(1, ceiling/fps)` with ceiling ≈ **1.3–1.4 k reports/s** fits every measured point (8812CU TX). **Attribution to emission-time, not transport:** 99.3% of unreported frames sit in SW_DEFINE tag gaps ≤ 2 (95 k singles + 47 k doubles at 2.39 k fps, max gap 28) — interleaved per-report loss, the signature of a rate limiter at report generation. Transport-batch loss (a dropped C2H aggregate) would lose contiguous dozens per event; the data shows the opposite. The tag-unwrap ground truth is trusted because the same runs' ledger joins matched 203,799/203,799 ok-frames in #359. **MISSED_RPT_NUM is dead on this fw:** constant 4 on every report across all runs — including 100%-coverage runs where nothing was dropped. Parse verified bit-exact against the 8822B/8822C/8822E vendor headers (`CCX_RPT_GET_MISSED_RPT_NUM`: dword +0x4, bits [15:13] — identical in all three trees), so this is fw behaviour, not a devourer bug. Tag gaps are the only drop signal available. ## What's in the PR - `tests/txrpt_coverage_attrib.py` — coverage, gap-shape histogram, missed-field distribution per recorded run (refuses tag-less J1 ledgers loudly). - `src/TxReport.h` — the `missed_rpt` field documents its measured deadness. - `docs/scheduled-mac.md` — the envelope + the consequence for accounting layers: keep the demanded report rate under ~1.3 k/s (SPE_RPT 1-in-N — step 2) or treat report-less frames as "unknown". ## Validation Three fresh on-air envelope runs (1.5 k/2 k/3 k fps demanded) + re-analysis of four recorded runs; tool parity-checked against the exploratory pass; `ctest` 48/48 (comment-only library change). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…loor form measured and rejected (#362) (#371) Closes #362 — with this, all four of the issue's acceptance items are met: the retry-limit curve and FEC dimensioning landed in #367 (steps 1+4), and steps 2–3 now have measured go/no-go with vendor citations plus the step-2 knob implemented. ## What the measurements found (recorded sweep data + three fresh arms) **The fw fallback ladder is already on** — every generation's inject path leaves DISDATAFB/DISABLE_FB at 0, and retried frames measurably step down: MCS3 → 54M → 24M → 18M → 9M → 6M, ~10% of retried frames finishing below the original rate. (My first issue comment quoted the *vendor's* injection path, which hard-disables fallback — devourer diverged; and a claimed J1 asymmetry was an NDPA-branch misread. Both corrected on the issue.) | arm (retry_limit 8, ~60 k reports) | retried final_rate | verdict | |---|---|---| | default | 4×57, 5×13, 7×17, 8×37, 11×51, 15×1240 | ladder confirmed | | **off** | **15×1200 — all pinned** | knob works exactly | | MCS0 floor | 15×24,642, **45×6,744 (= VHT1SS_MCS1!)**, 20× retry inflation, −1 pt delivery | **rejected** | ## What ships - `DEVOURER_TX_RETRY_FALLBACK=off` (`DeviceConfig::Tx::retry_fallback`) on all three generations — sets DISDATAFB/DISABLE_FB so retries re-air at the descriptor DATARATE, for constant-rate links where a 6M re-air of an MCS3 frame costs ~4× airtime. Unset = byte-identical current behaviour. - **Deliberately no floor form**: DATA_RTY_LOWEST_RATE is not a plain DESC_RATE bound on this fw — with the inject path's RA-group 9 the fw reinterprets the bound inside the group's rate space and the ladder wanders into VHT rates. The enum note carries the finding; the FrameParser headers keep the field position documented as deliberately unwritten; the env parse warns on any non-"off" value. - `docs/scheduled-mac.md` — measured distributions beside the retry-limit guidance; `DRONE_FALLBACK` passthrough in the arq harness. Step 3's disposition (also on the issue, vendor-cited): per-packet deadline NO-GO (no descriptor lifetime field on Jaguar3); per-AC PARTIAL via REG_PKT_LIFE_TIME 0x4C0 (1024 µs units) — effectively per-link since injection rides one queue; the fw's CCX state-2/3 lifetime signature must be bench-verified before anything trusts it. ## Validation Three fresh on-air arms (table above) + an off-arm hygiene smoke after the floor removal (888/888 pinned); `ctest` 49/49; default path byte-identical by construction. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Steps 1 and 4 of #362 (issue stays open for the rate-fallback and deadline research, steps 2–3).
What's added
tests/arq_retry_sweep.sh— runs the ARQ end-to-end delivery bench: three-ledger harness; count spsc-fat pool-exhaustion drops #359 arq_e2e bench once perDEVOURER_TX_RETRY_LIMIT, tabulates delivered% / drops / mean retries (the airtime proxy), and hands each run to the residual analyzer.tests/arq_fec_dimension.py— post-ARQ residual gap-length distribution per run: the quantity a wfb-style (K,N) block FEC must cover. Header states the caveats: single-gap-per-window mapping, near-field scope, and unreported-frames-count-as-lost (conservative — the safe direction for dimensioning).docs/scheduled-mac.md— the measured curve + recommendation next to the existing retry-knob paragraph.Measured (8812CU retrying TX → 8812EU duplex ground airing PixelPilot-shaped feedback bursts, ~1 k fps collision regime, near-field)
¹ single 62-frame gap in one run — one occurrence, P99 is still 3; not treated as a distribution feature.
Queue-time p99 is flat (~10–11 fw units) across all limits; only the rare worst case doubles (18 → 35) and then stops growing — deep retries are rare enough that latency does not enter the trade.
Recommendation carried in the docs:
DEVOURER_TX_RETRY_LIMIT=16on an ARQ link (zero bench loss at +5.4% retry airtime), or8plus a light FEC floor (K8/N11-class) where airtime is precious. Retries are backoff-spaced, so the field default of 3 can burn entirely inside one 2–3 ms ground-station feedback burst — that is where its 0.26% residual comes from.Also re-derived from the recorded #359/#366 ledgers (posted to #362): the stall-regime contrast — plain-async stalls produce ~110-frame contiguous gaps no sane FEC rate covers (rate 0.07), while the #366 backpressure policy bounds the same stalls to ≤13-frame gaps. FEC should be sized to the collision residual; stall regimes are a ring-policy problem.
Validation
Four full on-air bench runs (one per limit, ~129 k frames each, frame-exact three-ledger accounting from #359); analyzer parity-checked against the exploratory version on the recorded baseline.
ctest48/48 (no library changes in this PR).🤖 Generated with Claude Code